﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Fluq
{
	public sealed class ListDependencyResolver : IDependencyResolver
	{
		private IDependencyResolver _resolver;

		public ListDependencyResolver(IDependencyResolver resolver)
		{
			if (resolver == null)
			{
				throw new ArgumentNullException("resolver");
			}

			_resolver = resolver;
		}

		public bool TryResolve(object graphId, IDependencyContainer container, IDependencyKey key, out object instance)
		{
			if (!_resolver.TryResolve(graphId, container, key, out instance))
			{
				Type type = key.Type;

				if (type.IsGenericType)
				{
					Type genericType = type.GetGenericTypeDefinition();

					if (genericType == typeof(IList<>) || genericType == typeof(IEnumerable<>) || genericType == typeof(ICollection<>))
					{
						Type dependencyType = type.GetGenericArguments()[0];

						IListResolver resolver = (IListResolver)Activator.CreateInstance(typeof(ListResolver<>).MakeGenericType(dependencyType));

						ListDependency dependency = new ListDependency(resolver, graphId, key, new ListKey(dependencyType, key.Id));

						return container.Lifetime.TryAcquire(graphId, container, dependency, out instance);
					}
				}

				return false;
			}
			else
			{
				return true;
			}
		}

		public bool TryResolve(IDependencyContainer container, IDependencyKey key, out object instance)
		{
			return _resolver.TryResolve(container, key, out instance);
		}

		public bool TryRelease(IDependencyContainer container, object instance)
		{
			return _resolver.TryRelease(container, instance);
		}

		interface IListResolver
		{
			object Resolve(object graphId, IDependencyContainer container, IDependencyKey key);
		}

		sealed class ListResolver<T> : IListResolver
		{
			public object Resolve(object graphId, IDependencyContainer container, IDependencyKey itemKey)
			{
				IList<T> instances = new List<T>();

				foreach (IDependency dependency in container.Registry.Find(itemKey))
				{
					object instance;

					if (container.Lifetime.TryAcquire(graphId, container, dependency, out instance))
					{
						instances.Add((T)instance);
					}
				}

				if (instances.Any())
				{
					return instances;
				}

				return null;
			}
		}

		sealed class ListKey : IDependencyKey
		{
			private Type _type;
			private IComparable _id;

			public ListKey(Type type, IComparable id)
			{
				_type = type;
				_id = id;
			}

			public Type Type { get { return _type; } }

			public IComparable Id { get { return _id; } }
		}

		sealed class ListDependency : IDependency, IDependencyLifestyle
		{
			private IListResolver _listResolver;
			private IDependencyKey _listKey;
			private IDependencyKey _itemKey;
			private object _graphId;

			public ListDependency(IListResolver listResolver, object graphId, IDependencyKey listKey, IDependencyKey itemKey)
			{
				_listResolver = listResolver;
				_graphId = graphId;
				_listKey = listKey;
				_itemKey = itemKey;
			}

			public IDependencyKey Key { get { return _listKey; } }

			public Func<IDependencyContainer, IEnumerable<IDependencyParameter>, object> Factory
			{
				get
				{
					return (c, p) => _listResolver.Resolve(_graphId, c, _itemKey);
				}
			}

			public IDependencyLifestyle Lifestyle { get { return this; } }

			public object Acquire(object graphId, IDependencyKey key, Func<object> factory)
			{
				return factory();
			}

			public void Discard(IDependencyKey key, Action<object> disposer, object instance)
			{
				disposer(instance);
			}

			public Action<object> Disposer { get { return instance => { }; } }

			public IEnumerable<IDependencyParameter> Parameters { get { yield break; } }
		}
	}
}
